#include "TossBot.h"

vec3_t vZero={0.0,0.0,0.0};
vec3_t vFullForward={400.0,0.0,0.0};
vec3_t lastSureOrigin;

gamestate_t gs;
inventory_t inven;

int botSkill = 0;
bool botMobile = false;
path_t currentPath;
int pathTarget = 0;
int pathPoint = 0;
int samePlace = 0;

//trashtalk
char ttlines[MAX_TRASHTALKS][MAX_CHATLINE];
int trashTalks = 0;
time_t last_trashtalk;

void CheckLibAPIs()
{
	int errors = 0;
	if(qbGetAPIVersion()!=Q2BOT_API_VERSION)
	{
		Log_NewMessage("Incorrect Q2Bot.dll API version - %d not %d\r\n", qbGetAPIVersion(), Q2BOT_API_VERSION);
		errors++;
	}
	if(qmGetAPIVersion()!=Q2MAP_API_VERSION)
	{
		Log_NewMessage("Incorrect Q2Map.dll API version - %d not %d\r\n", qmGetAPIVersion(), Q2MAP_API_VERSION);
		errors++;
	}
	if(errors)
	{
		Log_NewMessage("Aborting, %d library errors\r\n", errors);
	}
	else
	{
		Log_NewMessage("Q2Bot.dll and Q2Map.dll loaded successfully\r\n");
	}
}

void LoadTrashTalk()
{
	FILE *ttfile;
	ttfile = fopen(ttFileName,"r");

	if(!ttfile)
	{
		Log_NewMessage("Trashtalk file failed to open.\n\r");
		return;
	}

	while(!feof(ttfile) && trashTalks<MAX_TRASHTALKS)
	{
		fgets(ttlines[trashTalks],MAX_CHATLINE-1,ttfile);
		trashTalks++;
	}

	fclose(ttfile);
	Log_NewMessage("Loaded %s for trashtalk lines\r\n", ttFileName);
}

void resetVars()
{
	 botSkill = 0;
	 botMobile = false;
	 pathTarget = 0;
	 pathPoint = 0;
	 samePlace = 0;
	 trashTalks = 0;
 
	 memset(&currentPath, 0, sizeof(currentPath));
}

void getNewPath()
{
	vec3_t dir;
	pathTarget = 0;
	int tries = 0;

	while(!pathTarget && (tries < 50)) // TODO: evaluate entities here - random entity for testing atm
	{
		tries++;
		
		int target = (rand()%(1023 - gs.max_players)) + gs.max_players;
		// random number between max_players and 1023 - normal ents section

		if(!gs.entities[target].modelindex)
			continue;

		if(target == gs.player_entity)
			continue;

		VectorSubtract(gs.entities[target].origin, gs.player.origin, dir);
		//if(VectorLength(dir) > 256)
		//	continue;

		if(qmFindPath(gs.player.origin, gs.entities[target].origin, &currentPath, true))
		{
			pathTarget = target;
		}
	}

	// as a fallback, target ourselves.
	pathTarget = gs.player_entity;
}

// TODO: Seperate parts of this out into other functions.
void botLoop()
{
	qbGetGameState(&gs);
	qbGetInventory(&inven);

	vec3_t dir, angles;
	bool visible;

	botMobile = true;

	if(!pathTarget)
		getNewPath();

	for(int i = 1; i < gs.max_players; i++)
	{
		if(i == gs.player_entity)
			continue;
		if(!gs.entities[i].modelindex)
			continue;

		VectorSubtract(gs.entities[i].origin, gs.player.origin, dir);

		angles[0] = pitchFromVect(dir);
		angles[1] = yawFromVect(dir);

		float frac = qmTraceLine(gs.player.origin, gs.entities[i].origin, CONTENTS_SOLID|CONTENTS_WINDOW);
		if(frac == 1.0)
			visible = true;
		else
			visible = false;

		if(botSkill == SKILL_DUMB && visible) // if you're dumb and you can see them, stop and fire away.
		{
			botMobile = false;
		}
	}

	if(botMobile && pathTarget)
	{
		VectorSubtract(currentPath.points[pathPoint], gs.player.origin, dir);

		if((pathPoint == (currentPath.length - 1)) && gs.entities[pathTarget].modelindex)
		{
			VectorSubtract(gs.entities[pathTarget].origin, gs.player.origin, dir);
		}
		else if(gs.entities[pathTarget].modelindex)
		{
			VectorSubtract(currentPath.points[pathPoint], gs.player.origin, dir);
			if(VectorLength(dir) < 10) // TODO: make path point leniency variable
			{
				pathPoint++;
				VectorSubtract(currentPath.points[pathPoint], gs.player.origin, dir);
			}
		}
		else
		{
			getNewPath();
		}

		angles[0] = pitchFromVect(dir);
		angles[1] = yawFromVect(dir);
		qbMovementOrder(angles,vFullForward,false);

		// this checks to see if the bot has moved less than 8 units per server frame 
		// uses sure_origin to check actual results rather than interpolation.
		if(lastSureOrigin)
		{
			vec3_t testdist;
			VectorSubtract(lastSureOrigin, gs.player.sure_origin, testdist);
			if(VectorLength(testdist) < 8)
				samePlace++;
			else
				samePlace = 0;
		}

		// if the bot hasn't moved more than 8 units per server frame for 4 seconds:
		// kill the q2map link and find a new path.
		if(samePlace >= 4*tickRate) // TODO: make this time configurable
		{
			Log_NewMessage("Warning: Marking link invalid after timeout\r\n");
			qmMarkLinkInvalid(pathPoint/2);

			samePlace = 0;

			getNewPath();
		}

		VectorCopy(gs.player.sure_origin, lastSureOrigin);
	}
	else
	{
		qbMovementOrder(angles,vZero,visible);
	}

	// TODO: make sure these don't repeat unless they have to
	// TODO: add specific event-triggered trashtalks
	if(trashTalks && (time(NULL) - last_trashtalk > 10) && !(rand()%(ttAverageFreq*tickRate)))
	{
		char buf[256];
		_snprintf(buf,sizeof(buf),"say \"%s\"",ttlines[rand()%trashTalks]);
		qbAsynchConsole(buf);
		last_trashtalk = time(NULL);
	}
}

void CreateBot()
{
	CheckLibAPIs();
	
	if(!qbStartup(Log_PrintN,Log_PrintN))
	{
		Log_Print("Q2BotCore Startup Failed!\r\n");
		return;
	}

	// TODO: GUI for name of demo and whether or not to record
	qbRecordDemo("debug.dm2");
	
	// extra q2map integration stuff - listed as alpha
	if(useTraceFunc)
	{
		qbSetTraceLineFunc(qmTraceLine);
		Log_NewMessage("Exporting qmTraceLine to q2bot...\r\n");
	}
	if(useBrushFunc)
	{
		qmSetBrushLocationFunc(qbGetBrushLocation);
		Log_NewMessage("Exporting qbGetBrushLocation to q2map...\r\n");
	}

	// TODO: userinfo
	qbUpdateUserinfo("name",botname);

	Log_NewMessage("Connecting to %s:%d",hostname,port);
	if(!qbConnect(hostname,port))
	{
		Log_Print("Connection Failed!\r\n");
		return;
	}
	Log_Print("Connected! Initialising...\r\n");

	char mapname[64];
	if(!qbGetModelString(1,mapname))
	{
		Log_Print("Error: no map\r\n"); // shouldn't happen
		return;
	}

	// TODO: make gamedir configurable.
	if(!qmLoadMap(quakeDir,"baseq2",mapname,Log_Print,Log_Print))
	{
		Log_Print("Map failed to load!\r\n");
		return;
	}

	qbBegin();

	clock_t nextFrameTime = 0;
	int fpsInterval; // time between frames, in ms

	fpsInterval = CLOCKS_PER_SEC / tickRate;
	
	resetVars();

	if(!trashTalks)
		LoadTrashTalk();

	// main loop
	while(qbIsConnected())
	{
		clock_t msClock;
		msClock = clock();

		if(msClock < nextFrameTime)
		{
			Sleep(1); // necessary?
			continue;
		}

		nextFrameTime = msClock + fpsInterval; // goes before frame to make system run full pace if behind

		botLoop();

		DoStats();
	}

	DoStats();

	Log_NewMessage("Q2Bot.dll reported disconnect.\r\n");

	// TODO: figure out a better way to clean this all up.
}